Skip to content

Handle Carbon 3 timestamp parsing and add boundary tests#1132

Open
dati18 wants to merge 8 commits into
mainfrom
carbon3-tests-update
Open

Handle Carbon 3 timestamp parsing and add boundary tests#1132
dati18 wants to merge 8 commits into
mainfrom
carbon3-tests-update

Conversation

@dati18
Copy link
Copy Markdown
Contributor

@dati18 dati18 commented May 19, 2026

  • MWTimestampHelperTest.php: Added coverage to ensure invalid MediaWiki timestamps are normalized into the helper’s expected InvalidFormatException behavior under Carbon 3.
  • SendEmptyWikiNotificationsJobTest.php: Tightened notification-threshold coverage so a wiki that is almost old enough (for example: 29.5 days old) does not trigger an empty-wiki notification early.
  • PlatformStatsSummaryJobTest.php: Added a test to ensure a second-precision lastEdit timestamp exactly at the inactivity cutoff is still counted as active.
  • ConversionMetricTest.php: Added a test to verify fractional day diffs are truncated to the integer values expected by the conversion metrics API.

Bug: T426592

@dati18 dati18 changed the title Handle Carbon 3 timestamp parsing and add boundary regression tests Handle Carbon 3 timestamp parsing and add boundary tests May 19, 2026
@dati18 dati18 force-pushed the carbon3-tests-update branch from 89a0590 to 069c041 Compare May 19, 2026 16:13
public static function getCarbonFromMWTimestamp(string $MWTimestamp): CarbonImmutable {
$carbon = CarbonImmutable::createFromFormat(self::MWTimestampFormat, $MWTimestamp);
if ($carbon === null) {
try {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm a bit unclear about this part.

  1. since Carbon 3 will throw an exception when createFormFormat, I'm not certain it helps to catch it just so we can throw our own. This could even make it harder to spot the reason for why it failed.
  2. the second check also seems obsolete since we either get a CarbonImmutable or an Exception gets thrown

So I think this method could be as simple as just line 19 - if something goes wrong an exception gets thrown as it used to. Is there more context for this additional logic that I'm not aware of?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you're right to question it, it's an overthinking part from me. My logic is createFromFormat() may throw a non-instance (null/false), so an extra instanceof guard prevents type error when returning $carbon.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah true, I didn't realize the returned value could also be null

Copy link
Copy Markdown
Member

@outdooracorn outdooracorn May 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@dati18 I like your defensive programming thinking here.

Your reply explains why the === null check was replaced with an instaceof check; but how come the catching and re-throwing of the same exception?

I agree with @deer-wmde here in thinking that allowing the InvalidFormatException to be thrown from createFromFormat() is enough. I'm thinking about the KISS principle.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thinking out loud some more, if CarbonImmutable::createFromFormat() only returns Carbon or null then if ($carbon === null) should be equivalent to if (!$carbon instanceof CarbonImmutable).

However, given that my PHP knowledge isn't good enough to be 100% confident that createFromFormat() won't return anything other than Carbon or null without throwing an error, and we haven't currently identified this as part of the codebase that needs optimizing, I'm happy to keep using the more explicit instaceof check. 🙂

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think instanceof is more robust against unexpected values and a safer overall check if behavior changes later. So we keep it?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I also updated the exception messages to be clearer and more precise.

Comment thread tests/Helper/MWTimestampHelperTest.php Outdated

public function testGetCarbonFromMWTimestampWithInvalidTimestamp() {
$this->expectException(InvalidFormatException::class);
$this->expectExceptionMessage('Unable to create Carbon object');
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As line 21 is expecting the Exception already, I struggle to see how this check helps us

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I just want to lock down the exact error message. a bit redundant, yes... WHat do you think? keep it or remove it?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes, this thought was connected to the other one. If we keep throwing the Exception ourselves I think it's fine to keep it, might even be worth to set the message to something that explicitly makes it obvious it comes from "us" and not just the library

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I also tend to shy away from validating error messages unless it is some sort of product requirement or is somehow crucial to the function of the unit under test. I find that in these situations you often end up creating more work for yourself (updating both class and test) without making the program/test suite any more robust.

@deer-wmde
Copy link
Copy Markdown
Contributor

The tighter tests look good to me overall
I left some comments on the Timestamp Helper

Comment thread app/Helper/MWTimestampHelper.php Outdated
try {
$carbon = CarbonImmutable::createFromFormat(self::MWTimestampFormat, $MWTimestamp);
} catch (InvalidFormatException $exception) {
throw new InvalidFormatException('Unable to create Carbon object', 0, $exception);
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this actually an Invalid Format Exception? I would assume that if it was one of those then CarbonImmutable::createFromFormat() would throw it. At a glance, this seems more like a RuntimeException to me.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch! I was thinking about how to answer this question. My knowledge is that InvalidFormatException is good for catching a single exception type for all "I can't parse MW timestamp" failures. A different type exception (like RuntimeException or UnexpectedValueException) is better if you want to distinguish what kind of exception was caught.
I think using InvalidFormatException is good enough; both cases check for input-parsing failures.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants